package cn.tjnext.apply.dao;

import cn.tjnext.apply.entity.AbstractEntity;
import cn.tjnext.apply.expections.EntityValidateFailedException;
import cn.tjnext.apply.repositories.AbstractRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.validation.ConstraintViolation;
import javax.validation.Validator;
import java.util.List;
import java.util.Set;

public abstract class AbstractEntityDao<T extends AbstractEntity, R extends AbstractRepository<T>> {

    //  Fields
    protected static final Logger log = LoggerFactory.getLogger(AbstractEntityDao.class);
    @Autowired
    protected Validator validator;
    @PersistenceContext
    protected EntityManager entityManager;

    //  Abstract methods
    protected abstract R getRepository();
    public abstract Sort getDefaultSorting();
    protected void deleteRelations(long id) {
        //  Do nothing in default implementation
    }

    //  Public methods
    public T findOne(Long pkId) {
        return getRepository().findOne(pkId);
    }

    public Iterable<T> findAll() {
        return findAllOrderByDefault();
    }

    public Iterable<T> findAllOrderByDefault() {
        if (getDefaultSorting() != null) {
            return getRepository().findAll(getDefaultSorting());
        }

        return getRepository().findAll();
    }

    public Page<T> findAll(Pageable pageable) {
        return getRepository().findAll(pageable);
    }

    public Page<T> findAllOrderByDefault(Pageable pageable) {
        Pageable newPageable = addDefaultSortToPageable(pageable);
        return getRepository().findAll(newPageable);
    }

    public long count() {
        return getRepository().count();
    }

    public T createOrSave(T entity) {
        validate(entity);
        getRepository().save(entity);
        return entity;
    }

    public void delete(long id) {
        T entity = findOne(id);
        if (entity == null) {
            return;
        }

        deleteRelations(id);
        getRepository().delete(id);
    }

    public void deleteAllIn(List<Integer> ids) {
        ids.forEach(this::delete);
    }

    //  Helper methods
    protected void validate(T entity) {
        assert entity != null;

        Set<ConstraintViolation<T>> constraintViolations = validator.validate(entity);
        if (constraintViolations.size() > 0) {
            throw new EntityValidateFailedException(constraintViolations.iterator().next().getMessage());
        }
    }

    protected Pageable getDefaultSortingPageable(int page, int size) {
        return new PageRequest(page, size, getDefaultSorting());
    }

    protected Pageable addDefaultSortToPageable(Pageable pageable) {
        if (pageable.getSort() != null)
            return pageable;

        if (getDefaultSorting() != null) {
            return new PageRequest(
                    pageable.getPageNumber(), pageable.getPageSize(), getDefaultSorting());
        }

        return pageable;
    }


    public abstract <T extends AbstractEntity> Page<T> searchByKeyword(String keyWord, Pageable pageable);
}
